package simpledb.buffer;


/**
 * The publicly-accessible buffer manager.
 * A buffer manager wraps a basic buffer manager, and
 * provides the same methods. The difference is that
 * the methods pin and pinNew will never return null.
 * If no buffers are currently available, then the
 * calling thread will be placed on a waiting list.
 * The waiting threads are removed from the list when 
 * a buffer becomes available.
 * If a thread has been waiting for a buffer for an
 * excessive amount of time (currently, 10 seconds)
 * then a {@link BufferAbortException} is thrown.
 * @author Edward Sciore
 */
public class BufferMgr {
	private static final long MAX_TIME = 1000; // 10 seconds
	private BasicBufferMgr bufferMgr;

	/**
	 * Creates a new buffer manager having the specified 
	 * number of buffers.
	 * @param numbuffers the number of buffer slots to allocate
	 */
	public BufferMgr(int numbuffers) {
		bufferMgr = new BasicBufferMgr(numbuffers);
	}

	/**
	 * Pins a buffer to the specified block, potentially
	 * waiting until a buffer becomes available.
	 * If no buffer becomes available within a fixed 
	 * time period, then a {@link BufferAbortException} is thrown.
	 * @param blk a reference to a disk block
	 * @return the buffer pinned to that block
	 */
	public synchronized Buffer pin(int blk) {
		try {
			long timestamp = System.currentTimeMillis();
			Buffer buff = bufferMgr.pin(blk);
			while (buff == null && !waitingTooLong(timestamp)) {
				wait(MAX_TIME);
				buff = bufferMgr.pin(blk);
			}
			if (buff == null)
				throw new BufferAbortException();
			return buff;
		}
		catch(InterruptedException e) {
			throw new BufferAbortException();
		}
	}

	/**
	 * Pins a buffer to a new block in the specified file, 
	 * potentially waiting until a buffer becomes available.
	 * If no buffer becomes available within a fixed 
	 * time period, then a {@link BufferAbortException} is thrown.
	 * @param filename the name of the file
	 * @param fmtr the formatter used to initialize the page
	 * @return the buffer pinned to that block
	 */
	public synchronized Buffer pinNew(PageFormatter fmtr) {
		try {
			long timestamp = System.currentTimeMillis();
			Buffer buff = bufferMgr.pinNew(fmtr);
			while (buff == null && !waitingTooLong(timestamp)) {
				wait(MAX_TIME);
				buff = bufferMgr.pinNew(fmtr);
			}
			if (buff == null)
				throw new BufferAbortException();
			return buff;
		}
		catch(InterruptedException e) {
			throw new BufferAbortException();
		}
	}

	/**
	 * Unpins the specified buffer. 
	 * If the buffer's pin count becomes 0,
	 * then the threads on the wait list are notified.
	 * @param buff the buffer to be unpinned
	 */
	public synchronized void unpin(Buffer buff) {
		bufferMgr.unpin(buff);
		if (!buff.isPinned())
			notifyAll();
	}
	
	public synchronized boolean unpinFree(Buffer buff) {
		int size = bufferMgr.unpinFree(buff);
		if (!buff.isPinned()){
			notifyAll();
		}
		if(size == -1) return false;
		else return true;
	}

	/**
	 * Flushes the dirty buffers modified by the specified transaction.
	 * @param txnum the transaction's id number
	 */
	public void flushAll(int txnum) {
		bufferMgr.flushAll(txnum);
	}

	/**
	 * Returns the number of available (ie unpinned) buffers.
	 * @return the number of available buffers
	 */
	public int available() {
		return bufferMgr.available();
	}

	private boolean waitingTooLong(long starttime) {
		return System.currentTimeMillis() - starttime > MAX_TIME;
	}

	public void testShowState(int number){
		bufferMgr.testShowState(number);
	}
}
